package com.gabejeff.game;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.util.Stack;

import javax.swing.JFrame;

import com.gabejeff.engine.GameMenu;
import com.gabejeff.engine.PlayerMenu;
import com.gabejeff.engine.Scene;
import com.gabejeff.engine.TiledScene;
import com.gabejeff.exceptions.InvalidSceneException;
import com.gabejeff.manager.InputManager;

public class Main extends JFrame {
		
	private Stack<Scene> world = new Stack<Scene>();

	private final static String GAME_NAME = "Herp da derp";
	private final static double VERSION = 0.000001;
	
	private BufferedImage tempScene = null;
	
	static double startTime = 0, diffTime, waitTime;
	
	public static void main(String[] args) {
		
		new Main();
		
	}
	
	public Main() {
		this.setTitle(GAME_NAME + " v" + String.format( "%1$f" , Main.VERSION));
		
		this.setSize(new Dimension(640,660));
		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setLocationRelativeTo(null);
		//this.setResizable(false);
		
		Scene.setFrame(this);
		TiledScene.setTileSize(32);
		
		TiledScene scene;
			
		scene = new TiledScene(50,50);
		this.setBackground(Color.WHITE);
		
		// Testing the texture tiles
		try {
			for (int row=0;row<50;row++) {
				for (int col=0;col<50;col++) {
					scene.setTileVal(row, col, 2);
				}
			}
			
			/* This sucks.  We should write a level builder */
			scene.setTileVal(4, 4, 7);
			scene.setTileVal(4, 5, 8);
			scene.setTileVal(4, 6, 8);
			scene.setTileVal(4, 7, 8);
			scene.setTileVal(4, 8, 8);
			scene.setTileVal(4, 9, 9);
			scene.setTileVal(5, 4, 10);
			scene.setTileVal(6, 4, 10);
			scene.setTileVal(7, 4, 10);
			scene.setTileVal(8, 4, 13);
			scene.setTileVal(8, 5, 14);
			scene.setTileVal(8, 6, 14);
			scene.setTileVal(8, 7, 14);
			scene.setTileVal(8, 8, 14);
			scene.setTileVal(8, 9, 15);
			scene.setTileVal(7, 9, 12);
			scene.setTileVal(6, 9, 12);
			scene.setTileVal(5, 9, 12);
			scene.setTileVal(8, 6, 11);
			scene.setTileVal(8, 7, 11);
			scene.setTileVal(8, 7, 11);
			scene.setTileVal(8, 8, 21);
			scene.setTileVal(5, 5, 3);
			scene.setTileVal(5, 6, 3);
			scene.setTileVal(5, 7, 3);
			scene.setTileVal(5, 8, 3);
			scene.setTileVal(6, 5, 3);
			scene.setTileVal(6, 6, 3);
			scene.setTileVal(6, 7, 3);
			scene.setTileVal(6, 8, 3);
			scene.setTileVal(7, 5, 3);
			scene.setTileVal(7, 6, 3);
			scene.setTileVal(7, 7, 3);
			scene.setTileVal(7, 8, 3);
			scene.setTileVal(8, 5, 19);
			scene.setTileVal(10,0,6);
			scene.setTileVal(10,1,6);
			scene.setTileVal(10,2,6);
			scene.setTileVal(10,3,6);
			scene.setTileVal(10,4,6);
			scene.setTileVal(10,5,6);
			scene.setTileVal(10,6,6);
			scene.setTileVal(10,7,6);
			scene.setTileVal(10,8,6);
			scene.setTileVal(10,9,6);
			scene.setTileVal(10,10,6);
			scene.setTileVal(9,10,6);
			scene.setTileVal(8,10,6);
			scene.setTileVal(7,10,6);
			scene.setTileVal(6,10,6);
			scene.setTileVal(5,10,6);
			scene.setTileVal(4,10,6);
			scene.setTileVal(4,11,6);
			scene.setTileVal(4,12,6);
			scene.setTileVal(4,13,6);
			scene.setTileVal(4,14,6);
			scene.setTileVal(4,15,6);
			scene.setTileVal(4,16,6);
			scene.setTileVal(4,17,6);
			scene.setTileVal(4,18,6);
			scene.setTileVal(4,19,6);
			
			for (int row=15;row<20;row++) {
				for (int col=10;col<20;col++) {
					scene.setTileVal(row, col, 5);
				}
			}
		} catch (InvalidSceneException ex) {
			ex.printStackTrace();
		}
		
		this.setFocusable(true);
		this.setVisible(true);

		world.push(scene);
				
		start();
		
	}

	public void start() {
		
		Dimension dim = new Dimension(this.getWidth(), this.getHeight());

		PlayerMenu p_menu = null;
		GameMenu m_menu = new GameMenu(true);
		// Prebuffer the menu to reduce latency when displaying the first time
		m_menu.preBuffer();
		
		Scene scene = null;
		InputManager inputManager = new InputManager();
		
		/* Attach the keylistener and request focus */
		this.addKeyListener(inputManager);
		this.requestFocus();
		
		int NONE = -1,INPUT;
		final int left = KeyEvent.VK_LEFT,
				  right = KeyEvent.VK_RIGHT,
				  up = KeyEvent.VK_UP,
				  down = KeyEvent.VK_DOWN,
				  inGameMenu = KeyEvent.VK_SPACE,
				  mainMenu = KeyEvent.VK_ESCAPE,
				  menuAction = KeyEvent.VK_ENTER;
		
		// Get the current scene
		scene = world.pop();
		// Prebuffer the scene to reduce initial latency
		scene.preBuffer();
		while( true ) {
			
			// Input
			INPUT = inputManager.getInput();

			// Update

			// Character update or action
			if( INPUT != NONE )
			{
				switch( INPUT )
				{
					case left:
						scene.moveLeft();
						break;
					case right:
						scene.moveRight();
						break;
					case up:
						scene.moveUp();
						break;
					case down:
						scene.moveDown();
						break;
					case inGameMenu:  // Not the new game menu but rather the in game menu for inventory and actions
						world.push( scene );
						scene.setMenu( p_menu );
						scene = p_menu;
						break;
					case mainMenu:
						if (!scene.equals(m_menu)) {
							world.push(scene);
							scene = m_menu;
						} else {
							/* If this menu is mult-level then pressing escape might
							 * back out of the sub-menu.  Let's let the menu decide what
							 * action to take.
							 */
							scene.performMenuAction(true);
						}
						break;
					case menuAction:
						scene.performMenuAction(false);
				}
			}

			// Special Tile Action -> Scripted Scene, Town, Dungeon, enemy encounter
			if( scene.hasAction() )
			{
				world.push( scene );
				scene = scene.getAction();

			// Ending a special action so we need old scene we were at
			}else if( scene.isDone() )
			{
				scene.setUndone();
				scene = world.pop();
				if ( tempScene != null ) {
					tempScene.flush();
					tempScene = null;
				}
			}

			// If the current scene is an overlay then we need to draw the previous scene
			// beneath the current scene.
			if (tempScene == null && scene.isOverlay() && !world.isEmpty()) {
				tempScene = world.peek().getSnapshot();
			} else if (tempScene != null) {
				scene.addUnderlying(tempScene);
			}
			
			// Paint the current scene
			scene.paint( this );
			
			// Limit the framerate to 30 frames per second.  This will guarantee that
			// the framereate does not exceed 30 fps.
			limitFrameRate(30);
			
		}
	}
	
	/**
	 * Limits the framerate to the supplied frames per second.
	 * @param fps The limiting framerate.
	 */
	private static void limitFrameRate(double fps) {
	    waitTime = 1000.0 / fps;
	    diffTime = System.currentTimeMillis() - startTime;
	    if (diffTime < waitTime) {
	    	try {
	    		Thread.sleep((long)(waitTime - diffTime));
	    	} catch (InterruptedException ex) {
	    		
	    	}
	    }
	    startTime = System.currentTimeMillis();
	}


}
